﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Xml.Linq;
using Wombat.Client.Core;

namespace Client.RawDataSimulator
{
    public partial class MainForm : Form
    {
        private IEnumerable<Formula> _formulas;
        private Formula _selectedFormula;
        private readonly Graphics _graphic;
        private long _tagId;
        private IClientContext _context;

        public MainForm()
        {
            InitializeComponent();
            InitFormulas();
            _graphic = GraphDisplay.CreateGraphics();
        }

        private static double? ConstantDf(Formula formula)
        {
            NextX(formula.State);
            return GetVar(formula.State, "y");
        }

        private static double? SinusDf(Formula formula)
        {
            var x = NextX(formula.State);
            var h = GetVar(formula.State, "h");
            var phase = GetVar(formula.State, "phase");
            var a = GetVar(formula.State, "a");

            return a * Math.Sin(x*h*Math.PI/180 + phase);
        }

        private static double? TanDf(Formula formula)
        {
            var x = NextX(formula.State);
            var h = GetVar(formula.State, "h");
            var phase = GetVar(formula.State, "phase");
            var a = GetVar(formula.State, "a");

            return a * Math.Tan(x*h*Math.PI/180 + phase);
        }

        readonly Random _random = new Random(DateTime.UtcNow.Millisecond);
        private double? RandomDf(Formula formula)
        {
            NextX(formula.State);
            var range = GetVar(formula.State, "r");

            return range*_random.NextDouble() - range/2;
        }

        private static double? TeethDf(Formula formula)
        {
            var x = NextX(formula.State);
            var m = GetVar(formula.State, "m");
            var h = GetVar(formula.State, "h");

            return (((int) (x*m))%(int) h)*m;
        }

        private static double GetVar(dynamic state, string var)
        {
            var stateDic = (IDictionary<string, object>)state;
            if (!stateDic.ContainsKey(var))
                return 0d;
            return (double) stateDic[var];

        }

        private static double NextX(dynamic state)
        {
            state.x = (state.x ?? -1.0d) + 1.0d;
            return (double) state.x;
        }

        private void InitFormulas()
        {
            _formulas = new[]
                {
                    new Formula {Name = "CTE", Caption = "Constant( x )", Parameters = new[] {"y"}, DataFactory = ConstantDf},
                    new Formula {Name = "SIN", Caption = "a * Sin( x * h * pi / 180 + phase )", Parameters = new[] {"a", "h", "phase"}, DataFactory = SinusDf},
                    new Formula {Name = "TAN", Caption = "a * Tan( x * h * pi / 180 + phase )", Parameters = new[] {"a", "h", "phase"}, DataFactory = TanDf},
                    new Formula {Name = "RND", Caption = "r * random()", Parameters = new [] {"r"}, DataFactory = RandomDf},
                    new Formula {Name = "TTH", Caption = "m * ( x % h )", Parameters = new [] {"m", "h"}, DataFactory = TeethDf}
                };

            FormulaList.DataSource = _formulas.Select(f => string.Format("{0}: {1}", f.Name, f.Caption)).ToList();
        }

        private void MainForm_Load(object sender, EventArgs e)
        {

        }

        private void FormulaList_SelectedIndexChanged(object sender, EventArgs e)
        {
            var formulaName = ((string)FormulaList.Items[FormulaList.SelectedIndex]).Substring(0, 3);
            var formula = _formulas.First(f => f.Name == formulaName);
            SelectFormula(formula);
        }

        private void SelectFormula(Formula formula)
        {
            _selectedFormula = formula;
            ResetDisplay();
            StopTimer();
            formula.State.x = 0.0d;

            ParametersText.Text =
                string.Join("\r\n", formula.Parameters.Select(p => p + " = "));
        }

        private void SetParamValues()
        {
            var paramValues = ParametersText.Lines
                                            .Where(l => !string.IsNullOrWhiteSpace(l))
                                            .Select(l => l.Split(new[] {'='}, 2))
                                            .Where(ar => ar.Length == 2)
                                            .Select(ar => new {Var = ar[0].Trim(), Val = ar[1].Trim()});
            foreach (var paramValue in paramValues)
            {
                var ps = (IDictionary<string, object>) _selectedFormula.State;
                if (ps.ContainsKey(paramValue.Var))
                    ps[paramValue.Var] = double.Parse(paramValue.Val);
                else
                    ps.Add(paramValue.Var, double.Parse(paramValue.Val));
            }
        }

        private void ResetDisplay()
        {
            if (_graphic != null) _graphic.Clear(Color.White);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            StartSimulation();
        }
    
        private void StartTimer()
        {
            Timer.Start();
            button1.Enabled = false;
            button2.Enabled = true;
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            if (_random.Next(100) <= RandomnessNumber.Value)
                return; 
            var val = _selectedFormula.DataFactory(_selectedFormula);
            var data = new Timed<double?>
                {
                    TagId = _tagId,
                    Time = DateTime.UtcNow,
                    Value = val
                };
            try
            {
                _context.SendDataDouble(data);
            }
            catch (Exception exception)
            {
                StopTimer();
                MessageBox.Show(@"Couldn't call the client" + exception);
            }
            PaintValue(val);
        }

        private void StopTimer()
        {
            Timer.Stop();
            button2.Enabled = false;
            button1.Enabled = true;
        }

        private void PaintValue(double? val)
        {
            var x = (double) _selectedFormula.State.x;
            if (!val.HasValue)
                return;

            var width = GraphDisplay.Width;
            var height = GraphDisplay.Height;
            var wLWidth = WaveLenBar.Value;
            var wLHeight = WaveHeightBar.Value;

            if (((int)x % wLWidth) == 0)
                ResetDisplay();
            var pX = ((double) ((int) x)%wLWidth)/wLWidth*width;
            var pY = height/2.0d - Math.Max(Math.Min(val.Value/(wLHeight)*height, 10000d), -10000d);
            _graphic.DrawEllipse(Pens.Black, (float)Math.Min(pX, float.MaxValue) - 1.5f, (float)pY - 1.5f, 3f, 3f);
        }

        private void WaveLenBar_ValueChanged(object sender, EventArgs e)
        {
            ResetDisplay();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            StopSimulation();
        }

        public void SetData(string formula, int tagId, int randomness, int interv, string paramVals)
        {
            FormulaList.SelectedItem = FormulaList.Items.Cast<object>().First(i => i.ToString().StartsWith(formula));
            TagIdText.Text = tagId.ToString(CultureInfo.InvariantCulture);
            RandomnessNumber.Text = randomness.ToString(CultureInfo.InvariantCulture);
            TimerIntTxt.Text = interv.ToString(CultureInfo.InvariantCulture);
            ParametersText.Text = paramVals;
        }

        public void StartSimulation()
        {
            _tagId = Int64.Parse(TagIdText.Text);
            var timerInt = Int32.Parse(TimerIntTxt.Text);
            SetParamValues();
            if (_context != null)
                _context.Dispose();
            _context = new ClientContextFactory().Create(
                XDocument.Load(Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location) ?? ".\\", "ClientConfig.xml")));

            Timer.Interval = timerInt;
            StartTimer();
        }

        public void StopSimulation()
        {
            StopTimer();
        }
    }
}
